/*
 * Print functions
 */
 
#include "print.h"
#include "util.h"
#include "video.h"

static int doprint( const char *fmt, va_list args, fnptr_t fn, void *ptr )
{
	unsigned state = 0, flags = 0, radix = 0, actual, count, given;
	unsigned char *where, buf[ PR_BUFLEN ];
	long num;

	state = flags = count = given = 0;

// Begin scanning format specifier list
	for ( ; *fmt; fmt++ )
	{
		switch ( state )
		{
		// STATE 0: AWAITING %
		case 0:
			if ( *fmt != '%' )	// not %...
			{
				fn ( *fmt, &ptr );	// ...just echo it
				count++;

				break;
			}

// Found %, get next character and advance state to check if next char is a flag
			state++;
			fmt++;
			// FALL THROUGH
			// STATE 1: AWAITING FLAGS ( % - 0 )
		case 1:
			if ( *fmt == '%' )	// %%
			{
				fn ( *fmt, &ptr );
				count++;
				state = flags = given = 0;

				break;
			}

			if ( *fmt == '-' )
			{
				if ( flags & PR_LJ )	// %-- is illegal
					state = flags = given = 0;
				else
					flags |= PR_LJ;

				break;
			}

// Not a flag char: advance state to check if it's field width
			state++;
// Check now for '%0...'

			if ( *fmt == '0' )
			{
				flags |= PR_LZ;
				fmt++;
			}

			// FALL THROUGH
			// STATE 2: AWAITING (NUMERIC) FIELD WIDTH
		case 2:
			if ( *fmt >= '0' && *fmt <= '9' )
			{
				given = 10 * given +
					( *fmt - '0' );

				break;
			}

// Not field width: advance state to check if it's a modifier
			state++;
			// FALL THROUGH
			// STATE 3: AWAITING MODIFIER CHARS ( FNlh )
		case 3:
			if ( *fmt == 'F' )
			{
				flags |= PR_FP;

				break;
			}

			if ( *fmt == 'N' )
				break;

			if ( *fmt == 'l' )
			{
				flags |= PR_32;

				break;
			}

			if ( *fmt == 'h' )
			{
				flags |= PR_16;

				break;
			}

// Not modifier: advance state to check if it's a conversion char
			state++;
			// FALL THROUGH
// STATE 4: AWAITING CONVERSION CHARS ( Xxpndiuocs )
		case 4:
			where = buf + PR_BUFLEN - 1;
			*where = '\0';

			switch ( *fmt )
			{
				case 'X':
					flags |= PR_CA;
				// FALL THROUGH
// xxx - far pointers (%Fp, %Fn) not yet supported
			case 'x':
			case 'p':
			case 'n':
				radix = 16;
				goto DO_NUM;
			case 'd':
			case 'i':
				flags |= PR_SG;
				// FALL THROUGH
			case 'u':
				radix = 10;
				goto DO_NUM;
			case 'o':
				radix = 8;
// Load the value to be printed. l=long=32 bits:
DO_NUM:				if ( flags & PR_32 )
					num = va_arg( args, unsigned long );

// h=short=16 bits ( signed or unsigned )
				else if ( flags & PR_16 )
				{
					if ( flags & PR_SG )
						num = va_arg( args, short );
					else
						num = va_arg( args, unsigned short );
				}

// No h nor l: sizeof(int) bits ( signed or unsigned )
				else
				{
					if ( flags & PR_SG )
						num = va_arg( args, int );
					else
						num = va_arg( args, unsigned int );
				}
// Take care of sign
				if ( flags & PR_SG )
				{
					if ( num < 0 )
					{
						flags |= PR_WS;
						num = -num;
					}
				}

// Convert binary to octal/decimal/hex ASCII
// The math here is ALWAYS unsigned
				do
				{
					unsigned long temp;

					temp = ( unsigned long ) num % radix;
					where--;

					if ( temp < 10 )
						*where = temp + '0';

					else if ( flags & PR_CA )
						*where = temp - 10 + 'A';

					else
						*where = temp - 10 + 'a';

					num = ( unsigned long ) num / radix;
				}

				while ( num != 0 );
				goto EMIT;

			case 'c':
// Disallow pad-left-with-zeroes for %c
				flags &= ~PR_LZ;
				where--;

				*where = ( unsigned char )va_arg(args, unsigned char);
				actual = 1;
				goto EMIT2;

			case 's':
// Disallow pad-left-with-zeroes for %s
				flags &= ~PR_LZ;
				where = va_arg( args, unsigned char * );
EMIT:
				actual = strlen( ( char* )where );

				if ( flags & PR_WS )
					actual++;

// If we pad left with ZEROES, do the sign now
				if ( ( flags & ( PR_WS | PR_LZ ) ) == ( PR_WS | PR_LZ ) )
				{
					fn( '-', &ptr );
					count++;
				}

// pad on left with spaces or zeroes ( for right justify )
EMIT2:				if ( ( flags & PR_LJ ) == 0 )
				{
					while ( given > actual )
					{
						fn( flags & PR_LZ ? '0' :
							' ', &ptr );
						count++;
						given--;
					}
				}

// If we pad left with SPACES, do the sign now
				if ( ( flags & ( PR_WS | PR_LZ ) ) == PR_WS )
				{
					fn( '-' , &ptr );
					count++;
				}

// Emit string/char/converted number
				while ( *where != '\0' )
				{
					fn( *where++, &ptr );
					count++;
				}

// pad on right with spaces ( for left justify )
				if ( given < actual )
					given = 0;

				else
					given -= actual;

				for ( ; given; given-- )
				{
					fn( ' ', &ptr );
					count++;
				}

				break;

			default:
				break;
			}
		default:
			state = flags = given = 0;

			break;
		}
	}

	return count;
}

static int printf_helper(unsigned ch, void **p)
{
	putch(ch);
	return 0;
}

int printf(char *fmt, ...)
{
	int r = 0;
	
	va_list list;
	va_start(list, fmt);
	
	r = doprint(fmt, list, printf_helper , NULL);
	
	va_end(list);
	
	return r;
}

int print_hex(int num)
{
	return printf("0x%x",num);
}

int print_dec(int num)
{
	return printf("%d",num);
}

int print_ch(char c)
{
	return printf("%c", c);
}
